
#import "WWSampleComponentList.h"
#import "Protocol_WWRenderable.h"

@implementation WWSampleComponentList

- init
{
  [super init];

  timestamp = -1.;
  cachedSample = [[WWSample alloc] init];
  // this isn't too smart, but to avoid surprises, you could...
  //[cachedSample setFreeData:NO];

  cacheIsDirty = YES;

  return self;

}

- initCount:(unsigned)numSlots
{
  [super initCount:numSlots];

  timestamp = -1.;
  cachedSample = [[WWSample alloc] init];
  // this isn't too smart, but to avoid surprises, you could...
  [cachedSample setFreeData:NO];
  cacheIsDirty = YES;

  return self;

}

- awake
{
  [super awake];

  timestamp = -1.;
  cachedSample = [[WWSample alloc] init];
  cacheIsDirty = YES;

  return self;

}


- free 
{ 
   //NXLogError("WWSampleComponentList %p at time %f being free'ed\n", self, timestamp);
   //NXLogError("WWSample freeing cachedSample %p\n", cachedSample);
   [cachedSample free];
   return [super free]; 
}


- addObject_:anObject { cacheIsDirty = TRUE; return [super addObject:anObject]; }

- setData:newData timestamp:(float)floatTime generator:(const char *)genName weight:(float)floatWeight
{
   WWSample    *newSample, *oldSample;
   BOOL        notFound;
   int         i, howMany;
   const char  *previousGenerator;


   // okay, so here's the deal.  Whenever we get sent this msg, we
   // first check and see if we have any data, i.e. ([self count]).  If we
   // don't, we malloc up a new WWSample using this info, stick it in our
   // list, set our timestamp to be this, then set the contents of our cache 
   // to new sample, mark the cache clean, and return.  Actually, we should be
   // lazy about it and only compute the cache as necessary...

   if (![self count])
   {  timestamp = floatTime;
      newSample = [[[WWSample alloc] init] setData:newData timestamp:floatTime generator:genName weight:floatWeight];
      [self addObject_:newSample];
   }
   else
   {  // need to see if we already have a sample from this source
      notFound = YES;
      i = 0;
      howMany = [self count];

      while (notFound && (i < howMany))
      {  oldSample = [self objectAt:i];
         previousGenerator = [oldSample generatorName];
         if (!genName)
         {  if (!previousGenerator) // they're both NULL; that's it; reuse the old sample
            {  [oldSample setData:newData timestamp:floatTime generator:genName weight:floatWeight];
               notFound = FALSE;
	    }
            else  // genName is NULL but this sample's not; it's not it
	    {}
	 }
         else // genName isn't NULL... 
         {  if (previousGenerator) // they're both not NULL; check the strings
            {  if (!strcmp(genName, previousGenerator))  // same generators; that's it
               {  [oldSample setData:newData timestamp:floatTime generator:genName weight:floatWeight];
                  notFound = FALSE;
 	       }
               else // nope, they're not the same...
               {}
	    }
            else  // genName is NULL but this sample's not; it's not it
	    {}
	 }
         i++;
      }
      if (notFound)
      {  newSample = [[[WWSample alloc] init] setData:newData timestamp:floatTime generator:genName weight:floatWeight];
         [self addObject_:newSample];
      }
   }
   cacheIsDirty = YES; 
   return self;
}


- setFreeData:(BOOL)flag
{
   return self;
}


- (BOOL)freeData
{
   return YES;
}


- (const char *)generatorName { return (const char *)NULL; }


- addObject:(id <WWSample>)anObject 
{ 
  if ([anObject conformsTo:@protocol(WWSample)])
  {  cacheIsDirty = TRUE; 
     return [self setData:[anObject data] timestamp:[anObject timestamp] generator:[anObject generatorName] weight:[anObject weight]];
  }
  NXLogError("object %s doesn't conform to the WWSample protocol: can't add it to the WWSampleComponentList", [[anObject class] name]);
  return nil;
}


- insertObject:anObject at:(unsigned)index { cacheIsDirty = TRUE; return [super insertObject:anObject at:(unsigned)index]; }
- removeObjectAt:(unsigned)index { cacheIsDirty = TRUE; return [super removeObjectAt:(unsigned)index]; }
- removeLastObject { cacheIsDirty = TRUE; return [super removeLastObject]; }
- replaceObjectAt:(unsigned)index with:newObject { cacheIsDirty = TRUE; return [super replaceObjectAt:(unsigned)index with:newObject]; }
- appendList: (List *)otherList { cacheIsDirty = TRUE; return [super appendList: (List *)otherList]; }
- addObjectIfAbsent:anObject { cacheIsDirty = TRUE; return [super addObjectIfAbsent:anObject]; }
- removeObject:anObject  { cacheIsDirty = TRUE; return [super removeObject:anObject]; }
- replaceObject:anObject with:newObject  { cacheIsDirty = TRUE; return [super replaceObject:anObject with:newObject]; }
- empty  { cacheIsDirty = TRUE; timestamp = -1; return [super empty]; }

- fillCache
{
   int            i = 1, 
                  howMany = [self count];
   float          u, weightThusFar;
   id <WWSample>  b = nil, 
                  newData = nil;


   if (cacheIsDirty)
   {  // yow.  time to fill dat der cache...
      
      if (timestamp == -1)
      {  // yow.  never set my timestamp
         timestamp = [[self objectAt:0] timestamp];
      }

      // okay, let's say we have 3 samples, the first two with weights
      // of 1, and the third has a weight of 2
      // a == .2, b == 3; c == 1
      // so we want d = ((.2 * 1) + (3 * 1) + (1 * 2))/(1 + 1 + 2) == (.2 + 3 + 2)/4 == 5.2/4

      // can we recreate this incrementally 
      // d1 = lerp between a and b
      // hmm... let's take a simpler example: 1 4 4; 9/3; 3
      // lerp the first two; .5 between
      // lerp the result and the next; .333 

      // hmm... let's take a simpler example: 1 4 4 7; 16/4; 4
      // lerp the first two; .5 between (2.5)
      // lerp the result and the next; .333 ((2.5 + (1.5/3)) == 3)
      // lerp the result and the next; .25 ((3 + (4/4)) == 4)


      // copy the first element to the cache
      newData = [[(WWSample *)[self objectAt:0] data] copy];
      [cachedSample setData:newData timestamp:timestamp generator:"cache" weight:1.0];
      weightThusFar = [[self objectAt:0] weight];
      while (i < howMany)
      {  b = [self objectAt:i];
         //u = (1./(float)(i+1)) * ([b weight]/(weightThusFar/i));

         u = 1./((float)(i+1));  // this version of calculating u ignores weights
         [[cachedSample data] lerpSelfWith:[b data] by:u];
         weightThusFar += [b weight];
         i++;
      }
   }
   cacheIsDirty = NO;

   return self;
}


- (float)timestamp {  [self fillCache];  return timestamp; }

- sample  {  [self fillCache];  return cachedSample; }


- data  {  [self fillCache];  return [cachedSample data]; }


- setWeight:(float)newWeight  {  return self;  }


- (float)weight  { return 1.0;  }

- (int)generatorCount { return [self count]; }

- writeEve:(NXStream *)stream atTabLevel:(int)tab
{
   int   howMany = [self count], 
         i, j = 0;


   //for (i = 0; i < tab; i++)
   //{  NXPrintf(stream, "\t");
   //}

   if (howMany)
   {  if (howMany > 1)
      {  [(id <WWRenderable>)[self objectAt:0] writeEve:stream atTabLevel:0];
         NXPrintf(stream, " \\\n");
         for (j = 1; j < (howMany-1); j++)
         {  for (i = 0; i < tab; i++)
            {  NXPrintf(stream, "\t");
            }
            for (i = 0; i < 33; i++) // magic numbers are evil, wave!!
            {  NXPrintf(stream, " ");
            }
            [(id <WWRenderable>)[self objectAt:j] writeEve:stream atTabLevel:0];
            NXPrintf(stream, " \\\n");
         }
         for (i = 0; i < tab; i++)
         {  NXPrintf(stream, "\t");
         }
         for (i = 0; i < 33; i++) // magic numbers are evil, wave!!
         {  NXPrintf(stream, " ");
         }
      }
      [(id <WWRenderable>)[self objectAt:j] writeEve:stream atTabLevel:0];
   }

   return self;
}

- write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime
{
   int   howMany = [self count], 
         i;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }

   // should look like this:
   // {$owner1 {$compiledSample1}} {$owner2 {$compiledSample1}} {$owner3 {$compiledSample3}}
   // so we want to actually get our samples to give us this info directly, and so I shouldn't
   // recurse down calling the same routine as this.  We need a new routine... actuallly, why
   // not just get the sample to write its eve code into the stream...
 

   NXPrintf(stream, "startShape %s; ", [[[self objectAt:i] class] name]);
   // need tab
   // need index (position in current list)
   NXPrintf(stream, "set __text__(colorTime%f) {1 1 1};", time);
   NXPrintf(stream, "set __text__(colorSurface%f) plastic;", time);
   NXPrintf(stream, "animatable: {Color $__text__(colorTime%f) }; ", time); // to make animation easy
   NXPrintf(stream, "animatable: {Surface $__text__(colorSurface%f)}; ", time); // to make animation easy

   NXPrintf(stream, 
	    "animatable: {Translate [expr { %d * $__text__(tabLength)}] ", tab);
   NXPrintf(stream, 
            "[expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] ", index);
   NXPrintf(stream, 
            "[expr {$__text__(sampleOffset) + ($__text__(timeFactor) * %f)}]};\n", time); 
   NXPrintf(stream, "  animatable: {WW3DText $__text__(fontName) $__text__(fontSize) {");


     for (i = 0; i < howMany; i++)
     {  [(id <WWRenderable>)[self objectAt:i] writeEve:stream atTabLevel:tab];
        // need to be moving stuff to the left here - no, since it's one string, it will be fine......
     }
   NXPrintf(stream, "} left;}\n");
  NXPrintf(stream, "endShape;\n");

  return self;
}



// boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details
- class { return [super class]; }
- (BOOL) conformsTo: (Protocol *)aProtocolObject { return [super conformsTo:aProtocolObject]; }

@end
